package webserver.bank;

import java.io.FileInputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Hashtable;

public class GenericObjectCreaterImpl extends ClassLoader implements
		GenericObjectCreater
{
	public static void main(String args[]) throws SecurityException,
			ClassNotFoundException, InstantiationException,
			IllegalAccessException, NoSuchMethodException,
			IllegalArgumentException, InvocationTargetException
	{
		Object[] par = { "example" };
		String[] types = { "java.lang.String" };
		Object obj = new GenericObjectCreaterImpl().createObject(
				"java.lang.String", par, types);
		System.out.println(obj);
	}

	private Hashtable<String, Class<?>> classes = new Hashtable<String, Class<?>>();

	@Override
	public Object createObject(String classname,
			Object[] constructorParameters, String[] constructorParameterTypes)
			throws ClassNotFoundException, InstantiationException,
			IllegalAccessException, SecurityException, NoSuchMethodException,
			IllegalArgumentException, InvocationTargetException
	{
		Class<?> classToLoad = Class.forName(classname, true, this);
		if (constructorParameters == null || constructorParameterTypes == null)
		{
			return classToLoad.newInstance();
		}
		else
		{
			Class<?> paramaterTypes[] = new Class[constructorParameterTypes.length];

			for (int i = 0; i < constructorParameterTypes.length; i++)
			{
				if (constructorParameterTypes[i].startsWith("int"))
					paramaterTypes[i] = int.class;
				else if (constructorParameterTypes[i].startsWith("byte"))
					paramaterTypes[i] = byte.class;
				else if (constructorParameterTypes[i].startsWith("short"))
					paramaterTypes[i] = short.class;
				else if (constructorParameterTypes[i].startsWith("long"))
					paramaterTypes[i] = long.class;
				else if (constructorParameterTypes[i].startsWith("char"))
					paramaterTypes[i] = char.class;
				else if (constructorParameterTypes[i].startsWith("float"))
					paramaterTypes[i] = float.class;
				else if (constructorParameterTypes[i].startsWith("double"))
					paramaterTypes[i] = double.class;
				else if (constructorParameterTypes[i].startsWith("boolean"))
					paramaterTypes[i] = boolean.class;
				else
					paramaterTypes[i] = Class
							.forName(constructorParameterTypes[i]);
			}

			Constructor<?> c = classToLoad.getConstructor(paramaterTypes);
			return c.newInstance(constructorParameters);
		}
	}

	private byte[] getClassImplFromDataBase(String className)
	{
		byte result[];
		try
		{
			FileInputStream fi = new FileInputStream("build/"
					+ className.replace('.', '/') + ".class");
			result = new byte[fi.available()];
			fi.read(result);
			return result;
		}
		catch (Exception e)
		{
			/*
			 * If we caught an exception, either the class wasnt found or it was
			 * unreadable by our process.
			 */
			e.printStackTrace();
			return null;
		}

	}

	/**
	 * This is a simple version for external clients since they will always want
	 * the class resolved before it is returned to them.
	 */
	@Override
	public Class<?> loadClass(String className) throws ClassNotFoundException
	{
		return (loadClass(className, true));
	}

	@Override
	public synchronized Class<?> loadClass(String className, boolean resolveIt)
			throws ClassNotFoundException
	{
		Class<?> result;
		byte classData[];

		/* Check our local cache of classes */
		result = classes.get(className);
		if (result != null)
		{
			return result;
		}

		/* Check with the primordial class loader */
		try
		{
			result = super.findSystemClass(className);
			return result;
		}
		catch (ClassNotFoundException e)
		{
		}

		/* Try to load it from our repository */
		classData = getClassImplFromDataBase(className);
		if (classData == null)
		{
			throw new ClassNotFoundException();
		}

		/* Define it (parse the class file) */
		result = defineClass(className, classData, 0, classData.length);
		if (result == null)
		{
			throw new ClassFormatError();
		}

		if (resolveIt)
		{
			resolveClass(result);
		}

		classes.put(className, result);
		return result;
	}

}
